from tensorflow.keras import layers
from tensorflow.keras.layers import (Activation, AveragePooling2D,
                                     BatchNormalization, Conv2D, Dense,
                                     Flatten, Input, MaxPooling2D,
                                     ZeroPadding2D)
from tensorflow.keras.models import Model

#用户加深网络
def identity_block(input_tensor, kernel_size, filters, stage, block):

    filters1, filters2, filters3 = filters

    conv_name_base = 'res' + str(stage) + block + '_branch'
    bn_name_base = 'bn' + str(stage) + block + '_branch'

    # 减少通道数
    x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor)
    x = BatchNormalization(name=bn_name_base + '2a')(x) #标准化
    x = Activation('relu')(x) #激活函数

    # 3x3卷积
    x = Conv2D(filters2, kernel_size,padding='same', name=conv_name_base + '2b')(x)
    x = BatchNormalization(name=bn_name_base + '2b')(x)
    x = Activation('relu')(x)
    
    # 上升通道数
    x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
    x = BatchNormalization(name=bn_name_base + '2c')(x)

    #将输入与主干部分进行相加
    x = layers.add([x, input_tensor])
    x = Activation('relu')(x)
    return x

#conv_block 是改变特征层的宽高和通道数
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
    filters1, filters2, filters3 = filters

    conv_name_base = 'res' + str(stage) + block + '_branch'
    bn_name_base = 'bn' + str(stage) + block + '_branch'

    #主干部分
    # 1*1的卷积 减少通道数
    x = Conv2D(filters1, (1, 1), strides=strides, name=conv_name_base + '2a')(input_tensor)
    x = BatchNormalization(name=bn_name_base + '2a')(x)
    x = Activation('relu')(x)

    # 3x3卷积 ,目的：特征提取
    x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x)
    x = BatchNormalization(name=bn_name_base + '2b')(x)
    x = Activation('relu')(x)

    # 1*1卷积 升通道数
    x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
    x = BatchNormalization(name=bn_name_base + '2c')(x)

    # 残差边部分
    #1*1的卷积 减少通道数
    shortcut = Conv2D(filters3, (1, 1), strides=strides,
                      name=conv_name_base + '1')(input_tensor)
    shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut)

    #主干部分和残差边部分进行相加
    x = layers.add([x, shortcut])
    x = Activation('relu')(x)#激活函数
    return x


def ResNet50(input_shape=[224,224,3], classes=2):
    img_input = Input(shape=input_shape)

    x = ZeroPadding2D((3, 3))(img_input)
    # 224,224,3 -> 112,112,64
    x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1')(x) #通道64，步长2*2：那么通道数变为64，宽和高减少一半
    x = BatchNormalization(name='bn_conv1')(x)
    x = Activation('relu')(x)

    # 112,112,64 -> 56,56,64
    x = MaxPooling2D((3, 3), strides=(2, 2))(x)#由于步长是2*2，宽和高减少一半

    # 56,56,64 -> 56,56,256
    x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1)) #步长为1*1，所以宽和高没有变化
    x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
    x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')

    # 56,56,256 -> 28,28,512
    x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='b')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='c')
    x = identity_block(x, 3, [128, 128, 512], stage=3, block='d')

    # 28,28,512 -> 14,14,1024
    x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e')
    x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f')

    # 14,14,1024 -> 7,7,2048 网络的特征提取部分完成
    x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
    x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
    x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')

    # 全局平均池化 获得 1,1,2048
    x = AveragePooling2D((7, 7), name='avg_pool')(x)

    # 进行预测
    # 平铺得到：2048
    x = Flatten()(x)

    # 全连接操作，num_classes。对图片进行分类
    #softmax操作：获得输入的图片属于每一个种类的概率
    x = Dense(classes, activation='softmax', name='fc1000')(x)

    model = Model(img_input, x, name='resnet50')

    return model

if __name__ == '__main__':
    model = ResNet50()
    model.summary()
